﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Security.Cryptography;

namespace Microsoft.Cryptography
{
    /// <summary>
    /// Base class for RSA-implementations
    /// </summary>
    public abstract class RSABase : RSA
    {
        /// <summary>
        /// Compute a hash value over a buffer and sign it.
        /// </summary>
        /// <param name="buffer">Buffer to sign.</param>
        /// <param name="halg">Hashalgorithm for signing.</param>
        /// <returns>Signing succeed.</returns>
        public abstract byte[] SignData(byte[] buffer, object halg);

        /// <summary>
        /// Sign an explicit hash value.
        /// </summary>
        /// <param name="rgbHash"></param>
        /// <param name="halg"></param>
        /// <returns>Signing succeed.</returns>
        public abstract byte[] SignHash(byte[] rgbHash, string halg);

        /// <summary>
        /// Verify the signature on a buffer of data.
        /// </summary>
        /// <param name="buffer"></param>
        /// <param name="halg"></param>
        /// <param name="signatureData"></param>
        /// <returns></returns>
        public abstract bool VerifyData(byte[] buffer, object halg, byte[] signatureData);

        /// <summary>
        /// Verify a signature from an explicit hash value.
        /// </summary>
        /// <param name="rgbHash"></param>
        /// <param name="halg"></param>
        /// <param name="signatureData"></param>
        /// <returns></returns>
        public abstract bool VerifyHash(byte[] rgbHash, object halg, byte[] signatureData);

        // PKCS #1 headers for various hash algorithms.
        
        /// <summary>
        /// PKCS #1 headers for MD5 hash algorithm.
        /// </summary>
        protected static readonly byte[] md5Header =
			{(byte)0x30, (byte)0x20, (byte)0x30, (byte)0x0C,
			 (byte)0x06, (byte)0x08, (byte)0x2A, (byte)0x86,
			 (byte)0x48, (byte)0x86, (byte)0xF7, (byte)0x0D,
			 (byte)0x02, (byte)0x05, (byte)0x05, (byte)0x00,
			 (byte)0x04, (byte)0x10};
        
        /// <summary>
        /// PKCS #1 headers for SHA1 hash algorithm.
        /// </summary>
        protected static readonly byte[] sha1Header =
			{(byte)0x30, (byte)0x21, (byte)0x30, (byte)0x09,
			 (byte)0x06, (byte)0x05, (byte)0x2B, (byte)0x0E,
			 (byte)0x03, (byte)0x02, (byte)0x1A, (byte)0x05,
			 (byte)0x00, (byte)0x04, (byte)0x14};

        /// <summary>
        /// PKCS #1 headers for SHA256 hash algorithm.
        /// </summary>
        protected static readonly byte[] sha256Header =
			{(byte)0x30, (byte)0x31, (byte)0x30, (byte)0x0D,
			 (byte)0x06, (byte)0x09, (byte)0x60, (byte)0x86,
			 (byte)0x48, (byte)0x01, (byte)0x65, (byte)0x03,
			 (byte)0x04, (byte)0x02, (byte)0x01, (byte)0x05,
			 (byte)0x00, (byte)0x04, (byte)0x20};

        /// <summary>
        /// PKCS #1 headers for SHA384 hash algorithm.
        /// </summary>
        protected static readonly byte[] sha384Header =
			{(byte)0x30, (byte)0x41, (byte)0x30, (byte)0x0D,
			 (byte)0x06, (byte)0x09, (byte)0x60, (byte)0x86,
			 (byte)0x48, (byte)0x01, (byte)0x65, (byte)0x03,
			 (byte)0x04, (byte)0x02, (byte)0x02, (byte)0x05,
			 (byte)0x00, (byte)0x04, (byte)0x30};

        /// <summary>
        /// PKCS #1 headers for SHA512 hash algorithm.
        /// </summary>
        protected static readonly byte[] sha512Header =
			{(byte)0x30, (byte)0x51, (byte)0x30, (byte)0x0D,
			 (byte)0x06, (byte)0x09, (byte)0x60, (byte)0x86,
			 (byte)0x48, (byte)0x01, (byte)0x65, (byte)0x03,
			 (byte)0x04, (byte)0x02, (byte)0x03, (byte)0x05,
			 (byte)0x00, (byte)0x04, (byte)0x40};

        /// <summary>
        /// PKCS #1 headers for Ripe-MD160 hash algorithm.
        /// </summary>
        protected static readonly byte[] ripeMd160Header =
			{(byte)0x30, (byte)0x21, (byte)0x30, (byte)0x09,
			 (byte)0x06, (byte)0x05, (byte)0x2B, (byte)0x24,
			 (byte)0x03, (byte)0x02, (byte)0x01, (byte)0x05,
			 (byte)0x00, (byte)0x04, (byte)0x14};

        /// <summary>
        /// Convert a "halg" value into a HashAlgorithm instance.
        /// </summary>
        /// <param name="halg"></param>
        /// <param name="hashName">Output-Parameter: represents the name of the hash algorithm.</param>
        /// <returns></returns>
        protected static HashAlgorithm ObjectToHashAlg(Object halg, out string hashName)
        {
            HashAlgorithm alg = null;
            hashName = string.Empty;

            if (halg == null)
            {
                throw new ArgumentNullException("halg");
            }
            else if (halg is String)
            {
                alg = HashAlgorithm.Create((String)halg);
            }
            else if (halg is HashAlgorithm)
            {
                alg = (HashAlgorithm)halg;
            }
            else if (halg is Type && ((Type)halg).IsSubclassOf(typeof(HashAlgorithm)))
            {
                alg = (HashAlgorithm)(Activator.CreateInstance((Type)halg));
            }
            else
            {
                throw new Exception(ErrorText.HashAlgNotSupported);
            }
            if (!(alg is SHA1) && !(alg is MD5) &&
               !(alg is SHA256) && !(alg is SHA384) &&
               !(alg is SHA512) && !(alg is RIPEMD160))
            {
                throw new Exception(ErrorText.HashAlgNotSupported);
            }
            else
            {
                if (alg is SHA1)
                    hashName = "SHA1";
                else if (alg is SHA256)
                    hashName = "SHA256";
                else if (alg is SHA384)
                    hashName = "SHA384";
                else if (alg is SHA512)
                    hashName = "SHA512";
                else if (alg is MD5)
                    hashName = "MD5";
                else if (alg is RIPEMD160)
                    hashName = "RIPEMD160";
            }

            return alg;
        }

        /// <summary>
        /// RFC 3447
        /// 9.2 EMSA-PKCS1-v1_5
        /// 
        /// This encoding method is deterministic and only has an encoding
        /// operation.
        /// 
        /// EMSA-PKCS1-v1_5-ENCODE (M, emLen)
        /// 
        /// Option:
        /// Hash     hash function (hLen denotes the length in octets of the hash
        ///          function output)
        /// 
        /// Input:
        /// M        message to be encoded
        /// emLen    intended length in octets of the encoded message, at least
        ///          tLen + 11, where tLen is the octet length of the DER
        ///          encoding T of a certain value computed during the encoding
        ///          operation
        /// 
        /// Output:
        /// EM       encoded message, an octet string of length emLen
        /// 
        /// Errors:
        /// "message too long"; "intended encoded message length too short"
        /// 
        /// Steps:
        /// 
        ///    1. Apply the hash function to the message M to produce a hash value
        ///       H:
        /// 
        ///         H = Hash(M).
        /// 
        ///         If the hash function outputs "message too long," output "message
        ///         too long" and stop.
        /// 
        /// 2. Encode the algorithm ID for the hash function and the hash value
        /// into an ASN.1 value of type DigestInfo (see Appendix A.2.4) with
        /// the Distinguished Encoding Rules (DER), where the type DigestInfo
        /// has the syntax
        /// 
        ///     DigestInfo ::= SEQUENCE {
        ///         digestAlgorithm AlgorithmIdentifier,
        ///         digest OCTET STRING
        ///     }
        /// 
        /// The first field identifies the hash function and the second
        /// contains the hash value.  Let T be the DER encoding of the
        /// DigestInfo value (see the notes below) and let tLen be the length
        /// in octets of T.
        /// 
        /// 3. If emLen tLen + 11, output "intended encoded message length too
        /// short" and stop.
        /// 
        /// 4. Generate an octet string PS consisting of emLen - tLen - 3 octets
        /// with hexadecimal value 0xff.  The length of PS will be at least 8
        /// octets.
        /// 
        /// 5. Concatenate PS, the DER encoding T, and other padding to form the
        /// encoded message EM as
        /// 
        /// EM = 0x00 || 0x01 || PS || 0x00 || T.
        /// 
        /// 6. Output EM.
        /// </summary>
        /// <param name="emLen">length of the encoded message</param>
        /// <param name="digestHeader">PKCS#1 header for the digest</param>
        /// <param name="hash">buffer, which should be formatted</param>
        /// <returns></returns>
        protected static byte[] RsaPkcs1Padding(int emLen, byte[] digestHeader, byte[] hash)
        {
            //Create byte[] T with ASN.1 encoding 
            // DigestInfo ::= 
            //  SEQUENCE {
            //      digestAlgorithm AlgorithmIdentifier,
            //      digest OCTET STRING
            // }

            byte[] T = new byte[(digestHeader.Length) + hash.Length];
            Buffer.BlockCopy(digestHeader, 0, T, 0, digestHeader.Length);
            Buffer.BlockCopy(hash, 0, T, digestHeader.Length, hash.Length);

            byte[] EM = new byte[emLen];
            int tLen = T.Length;

            if (emLen < tLen + 11)
            {
                throw new Exception(ErrorText.RSAKeyTooShort);
            }

            byte[] PS = new byte[emLen - tLen - 3];

            for (int i = 0; i < PS.Length; i++)
            {
                PS[i] = 0xFF; //255
            }
            EM[0] = 0;
            EM[1] = 1;

            Buffer.BlockCopy(PS, 0, EM, 2, PS.Length);
            EM[PS.Length + 2] = 0;
            Buffer.BlockCopy(T, 0, EM, PS.Length + 3, T.Length);

            return EM;
        }


    }
}
